/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.netbeans.modules.emacs; import java.awt.*; import java.awt.event.*; import java.beans.*; import java.io.*; import java.net.*; import java.util.*; import javax.swing.*; import javax.swing.text.*; // XXX import org.openide.util.Utilities; import org.openide.TopManager; import org.openide.nodes.Node; import org.openide.util.actions.NodeAction; import org.openide.windows.TopComponent; public class EmacsKit extends StyledEditorKit implements Protocol { // private static final int bufsize = 4096; private transient EmacsProxier proxy = null; private transient boolean hosedProxy = false; private transient EmacsDocument theDoc = null; private transient EmacsCaret theCaret = null; private String contentType = null; /** * @associates Document */ private transient final Map panes = new HashMap (); // Map<JEditorPane, Document> /** * @associates PropertyChangeListener */ private transient final Map paneLists = new HashMap (); // Map<JEditorPane, PropertyChangeListener> /** * @associates ComponentListener */ private transient final Map paneCompLists = new HashMap (); // Map<JEditorPane, ComponentListener> private transient final EmacsListener keyListener = new EmacsListener () { public void callback (EmacsEvent ev) { if (EVT_keyCommand.equals (ev.getType ())) { Object[] args = ev.getArgs (); if (args.length != 1) throw new RuntimeException (); String key = (String) args[0]; if (Connection.DEBUG) System.err.println("keyboard callback: " + key); KeyStroke ks = Utilities.stringToKey (key); if (Connection.DEBUG) System.err.println("Keystroke: " + ks); if (ks == null) { if (Connection.DEBUG) System.err.println("Warning: bogus key"); return; } Action action = TopManager.getDefault ().getGlobalKeymap ().getAction (ks); // XXX many X configs send Meta from the Alt key, so this is translated // here since the IDE conventionally uses Alt keybindings if (action == null && (ks.getModifiers () & InputEvent.META_MASK) != 0) { if (Connection.DEBUG) System.err.println("Meta-bound key not found, trying Alt instead"); ks = KeyStroke.getKeyStroke (ks.getKeyCode (), ks.getModifiers () & ~ InputEvent.META_MASK | InputEvent.ALT_MASK); action = TopManager.getDefault ().getGlobalKeymap ().getAction (ks); } if (Connection.DEBUG) System.err.println("Keystroke: " + ks); if (action == null) { if (Connection.DEBUG) System.err.println("Warning: key not defined"); return; } // XXX This is not great, either: e.g. Compile will *not* be enabled // often because the node selection is wrong. No public way to test whether // it should be enabled, since enable(Node[]) is protected. Could use // Java reflection I suppose. /* if (! action.isEnabled ()) { if (Connection.DEBUG) System.err.println ("Will not call action because it is disabled: " + action.getValue (Action.NAME)); return; } */ if (! (action instanceof NodeAction)) { if (Connection.DEBUG) System.err.println("Will call as a simple action: " + action.getValue (Action.NAME)); action.actionPerformed (new ActionEvent (EmacsKit.this, ActionEvent.ACTION_PERFORMED, key)); } else { if (Connection.DEBUG) System.err.println("Will call as a NodeAction: " + action.getValue (Action.NAME)); Set s = panes.keySet (); if (s.isEmpty ()) { if (Connection.DEBUG) System.err.println("no JEditorPane's to handle keystroke"); return; } if (s.size () > 1) { if (Connection.DEBUG) System.err.println(">1 JEditorPane's to handle keystroke, ambiguous"); return; } JEditorPane pane = (JEditorPane) s.iterator ().next (); TopComponent tc = null; for (Component c = pane; c != null; c = c.getParent ()) { if (c instanceof TopComponent) { tc = (TopComponent) c; break; } } if (tc == null) { // XXX could also check for nodeDelegate from ED's DataObject, if any, // but this should never be necessary if (Connection.DEBUG) System.err.println("Warning: JEditorPane was not within a TC, will not handle keystroke"); return; } Node[] nodes = tc.getActivatedNodes (); if (nodes.length != 1) { // XXX problem: apparently unselected JavaEditor components // often have no node selection at all! if (Connection.DEBUG) System.err.println("Warning: TC had 0 or >1 activated nodes, will ignore keystroke"); return; } action.actionPerformed (new ActionEvent (nodes[0], ActionEvent.ACTION_PERFORMED, key)); } } } }; public EmacsKit () { } public synchronized String toString () { if (proxy == null) return "EmacsKit[noproxy]"; else return "EmacsKit[" + proxy + "]"; } public synchronized Object clone () { if (Connection.DEBUG) { try { System.err.println("cloning to EmacsKit (possibly from Repository)..."); Class clazz = org.openide.TopManager.getDefault ().currentClassLoader ().loadClass ("org.netbeans.modules.emacs.EmacsKit"); Object o = clazz.newInstance (); if (contentType != null) { java.lang.reflect.Method m = clazz.getMethod ("setContentType", new Class[] { String.class }); m.invoke (o, new Object[] { contentType }); } return o; } catch (Exception e) { e.printStackTrace (); return null; } } else { EmacsKit nue = new EmacsKit (); nue.setContentType (getContentType ()); return nue; } } synchronized EmacsProxier getProxy () { if (Connection.DEBUG) System.err.println("maybe getting proxy"); if (proxy == null && panes.size () > 0 && ! hosedProxy) { if (Connection.DEBUG) System.err.println("creating new proxy"); try { EmacsSettings def = EmacsSettings.DEFAULT; proxy = new EmacsProxier (def.getHost (), def.isPassive () ? 0 : def.getPort (), def.getPassword ()); } catch (IOException e) { e.printStackTrace (); hosedProxy = true; return null; } if (contentType != null) { proxy.call (CMD_setContentType, new Object[] { contentType }); } getDoc ().init (proxy); proxy.addEmacsListener (keyListener); } return proxy; } synchronized EmacsDocument getDoc () { if (theDoc == null) theDoc = new EmacsDocument (); return theDoc; } synchronized EmacsCaret getCaret () { if (theCaret == null) theCaret = new EmacsCaret (); return theCaret; } public synchronized String getContentType () { return contentType; } public synchronized void setContentType (String ct) { contentType = ct; if (proxy != null) { proxy.call (CMD_setContentType, new Object[] { ct }); } } public synchronized void install (final JEditorPane pane) { if (Connection.DEBUG) System.err.println("EmacsKit installed"); super.install (pane); Document doc = pane.getDocument (); panes.put (pane, doc); if (doc != null && doc instanceof EmacsDocument) { if (Connection.DEBUG) System.err.println("Initting doc"); ((EmacsDocument) doc).init (getProxy ()); notifyPositioning (pane, doc); } PropertyChangeListener pclist = new PropertyChangeListener () { public void propertyChange (PropertyChangeEvent ev) { if ("document".equals (ev.getPropertyName ())) { Document doc1 = (Document) panes.get (pane); if (doc1 != null && doc1 instanceof EmacsDocument) { ((EmacsDocument) doc1).shutdown (); } Document doc2 = pane.getDocument (); panes.put (pane, doc2); if (doc2 != null && doc2 instanceof EmacsDocument) { ((EmacsDocument) doc2).init (getProxy ()); notifyPositioning (pane, doc2); } } } }; paneLists.put (pane, pclist); pane.addPropertyChangeListener (pclist); ComponentListener complist = new ComponentListener () { public void componentHidden (ComponentEvent ev) { update (); } public void componentShown (ComponentEvent ev) { update (); } public void componentResized (ComponentEvent ev) { update (); } public void componentMoved (ComponentEvent ev) { update (); } private void update () { Document doc3 = pane.getDocument (); if (doc3 != null) { notifyPositioning (pane, doc3); } } }; paneCompLists.put (pane, complist); pane.addComponentListener (complist); if (! (pane.getCaret () instanceof EmacsCaret)) pane.setCaret (getCaret ()); pane.setKeymap (new AsUserKeymap (pane.getKeymap ())); } private static void notifyPositioning (JEditorPane pane, Document doc) { if (pane.isShowing ()) { // Look to see if it is being scrolled, in which case reported position // and size are in fact the presumed values, not what is actually seen: Component displayed = pane; for (Component walk = pane; walk != null; walk = walk.getParent ()) if (walk instanceof JScrollPane) displayed = walk; Point p = displayed.getLocationOnScreen (); Dimension d = displayed.getSize (); if (p.x < 0 || p.y < 0 || d.height < 0 || d.width < 0) { if (Connection.DEBUG) System.err.println ("WARNING: Bad dimensions: " + p + " " + d); } else { doc.putProperty (PROP_locAndSize, new Rectangle (p, d)); } } else { doc.putProperty (PROP_locAndSize, new Rectangle (0, 0, 0, 0)); } } public synchronized void deinstall (JEditorPane pane) { if (Connection.DEBUG) System.err.println("EmacsKit deinstalled"); Document doc = pane.getDocument (); if (doc != null && doc instanceof EmacsDocument) { if (Connection.DEBUG) System.err.println("Switching off doc"); ((EmacsDocument) doc).shutdown (); } PropertyChangeListener pclist = (PropertyChangeListener) paneLists.get (pane); if (pclist != null) { paneLists.remove (pane); pane.removePropertyChangeListener (pclist); } ComponentListener complist = (ComponentListener) paneCompLists.get (pane); if (complist != null) { paneCompLists.remove (pane); pane.removeComponentListener (complist); } panes.remove (pane); if (panes.size () == 0) { if (proxy != null) { if (Connection.DEBUG) System.err.println("Closing down proxy due to uninstall from last pane"); // getDoc ().shutdown (); proxy.close (); proxy = null; } } super.deinstall (pane); } public synchronized Caret createCaret () { if (Connection.DEBUG) System.err.println("EmacsKit: creating caret"); return getCaret (); } public Document createDefaultDocument () { if (Connection.DEBUG) System.err.println("EmacsKit: getting the doc"); return getDoc (); } // ACTION-WRAPPING STUFF public Action[] getActions () { if (Connection.DEBUG) System.err.println("EmacsKit.getActions"); return adjustActions (super.getActions ()); } private Action[] lastOriginalActions = null; private Action[] lastAdjustedActions = null; private synchronized Action[] adjustActions (Action[] original) { if (! Utilities.compareObjects (original, lastOriginalActions) && ! Utilities.compareObjects (original, lastAdjustedActions)) { lastOriginalActions = original; lastAdjustedActions = new Action[original.length]; for (int i = 0; i < original.length; i++) lastAdjustedActions[i] = makeAUA (original[i]); } return lastAdjustedActions; } private class AsUserAction implements Action { final Action orig; AsUserAction (Action orig) { this.orig = orig; } public void actionPerformed (final ActionEvent ev) { if (Connection.DEBUG) System.err.println("EmacsKit.AsUserAction.actionPerformed on " + orig.getValue (NAME)); try { // XXX rather than getDoc(), would be better to use e.g.: // 1. ev.getSource() // 2. try cast to JEditorPane // 3. getDocument() // 4. try cast to EmacsDocument (or NbDocument.WriteLockable) getDoc ().runAtomicAsUser (new Runnable () { public void run () { orig.actionPerformed (ev); } }); } catch (BadLocationException ble) { Toolkit.getDefaultToolkit ().beep (); } } public void addPropertyChangeListener (PropertyChangeListener pcl) { orig.addPropertyChangeListener (pcl); } public void removePropertyChangeListener (PropertyChangeListener pcl) { orig.removePropertyChangeListener (pcl); } public Object getValue (String key) { return orig.getValue (key); } public void putValue (String key, Object value) { orig.putValue (key, value); } public boolean isEnabled () { return orig.isEnabled (); } public void setEnabled (boolean b) { orig.setEnabled (b); } } /** * @associates Action */ private final Map asUserActionCache = new WeakHashMap (); private Action makeAUA (Action a) { if (a == null) return null; Action res = (Action) asUserActionCache.get (a); if (res == null) { res = new AsUserAction (a); asUserActionCache.put (a, res); } return res; } private class AsUserKeymap implements Keymap { private Keymap orig; AsUserKeymap (Keymap orig) { this.orig = orig; } public String getName () { return orig.getName (); } public Action getDefaultAction () { return makeAUA (orig.getDefaultAction ()); } public void setDefaultAction (Action a) { orig.setDefaultAction (a); } public Action getAction (KeyStroke key) { return makeAUA (orig.getAction (key)); } public KeyStroke[] getBoundKeyStrokes () { return orig.getBoundKeyStrokes (); } public Action[] getBoundActions () { return adjustActions (orig.getBoundActions ()); } public KeyStroke[] getKeyStrokesForAction (Action a) { if (a instanceof AsUserAction) a = ((AsUserAction) a).orig; return orig.getKeyStrokesForAction (a); } public boolean isLocallyDefined (KeyStroke key) { return orig.isLocallyDefined (key); } public void addActionForKeyStroke (KeyStroke key, Action a) { orig.addActionForKeyStroke (key, a); } public void removeKeyStrokeBinding (KeyStroke keys) { orig.removeKeyStrokeBinding (keys); } public void removeBindings () { orig.removeBindings (); } public Keymap getResolveParent () { return new AsUserKeymap (orig.getResolveParent ()); } public void setResolveParent (Keymap parent) { if (parent instanceof AsUserKeymap) parent = ((AsUserKeymap) parent).orig; orig.setResolveParent (parent); } } }